﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Data;
using System.Globalization;
using System.Linq;
using System.Resources;
using System.Text;
using System.Windows;
using System.Windows.Data;
using System.Windows.Shapes;
using GBlason.Common.Attribute;
using GBlason.Common.CustomCommand;
using GBlason.Common.Tools;
using GBlason.Culture;
using System.Reflection;
using GBlason.ViewModel.CoatOfArm;

namespace GBlason.ViewModel.Contract
{
    /// <summary>
    /// Contract for all the different layers and component of the tree logic coat of arms composition
    /// Help defining all the data for the properties display
    /// Help defining all the links for the treeview display
    /// Help defining all the path for the graphical flattened display (CoAViewer)
    /// The CoA logic is divided in 2 differents kind of objects : the CoatOfArmComponent which is a path with properties.
    /// And the properties, which are bound to the values in the CoatOfArmComponent concrete implementations
    /// </summary>
    public abstract class CoatOfArmComponent : INotifyPropertyChanged, IShapable, IChildContainer, ICommandTarget, ITreeViewSearchable
    {
        protected CoatOfArmComponent()
        {
            _children = new ObservableCollection<CoatOfArmComponent>();
        }

        #region Properties

        /// <summary>
        /// Gets the name of the component. Have to exist in the resource (the type is used as the resource key)
        /// </summary>
        /// <value>
        /// Get the name of the component or String.Empty if no resources are found for the given type.
        /// </value>
        public virtual String ComponentName
        {
            get
            {
                var rman = new ResourceManager(typeof(BlasonVocabulary));
                return rman.GetString(GetType().Name) ?? String.Empty;
            }
        }

        /// <summary>
        /// Gets or sets the origin object (from the repository).
        /// The repository objects are used for saving and loading, they are not intended to store the data at run time or in edition mode.
        /// They are rewritten in saves scenarii (auto or manual)
        /// </summary>
        /// <value>
        /// The origin object.
        /// </value>
        public abstract Object OriginObject { get; set; }

        /// <summary>
        /// Gets the properties data.
        /// They are loaded each time by reflection (each time because we might need to update the value)
        /// This can be overridable in th concrete implementation if the too numerous properties are slowing down the reflection.
        /// Or if inherited properties have to be considered (not good behavior though)
        /// </summary>
        public virtual ObservableCollection<PropertyDisplayer> PropertiesData
        {
            get
            {
                var type = GetType();
                var retour = new ObservableCollection<PropertyDisplayer>();
                var rm = new ResourceManager(typeof(BlasonVocabulary));

                var notInheritedProperties = from propertyInfo in type.GetProperties()
                                             let attr = propertyInfo.GetCustomAttributes(true)
                                             where attr.Any(at => at is CoaPropertyAttribute)
                                             let propAttr =
                                                 attr.First(at => at is CoaPropertyAttribute) as CoaPropertyAttribute
                                             where propAttr != null
                                             select new PropertyDisplayer
                                                        {
                                                            PropertyName =
                                                                rm.GetString(propAttr.NameResourceKey,
                                                                             CultureInfo.CurrentCulture),
                                                            PropertyValue = propertyInfo.GetValue(this, null),
                                                            PropertyValueType = propertyInfo.PropertyType
                                                        };

                foreach (var toAdd in notInheritedProperties)
                {
                    retour.Add(toAdd);
                }
                return retour;
            }
        }

        #endregion

        #region ICommandTarget

        #endregion

        #region IShapable

        /// <summary>
        /// Gets or sets the geometry.
        /// </summary>
        /// <value>
        /// The geometry.
        /// </value>
        public abstract string Geometry { get; set; }

        /// <summary>
        /// Updates the shape from the parent's Shape using the same rule that when created.
        /// </summary>
        public abstract void UpdateShapeFromParent();

        #endregion

        #region IChildContainer
        /// <summary>
        /// Gets or sets the parent.
        /// </summary>
        /// <value>
        /// The parent.
        /// </value>
        public IChildContainer Parent { get; set; }

        /// <summary>
        /// Gets the root parent.
        /// </summary>
        public IChildContainer RootParent
        {
            get
            {
                return Parent == null ? this : Parent.RootParent;
            }
        }

        public delegate void ChildrenChangedEventHandler(CoatOfArmComponent parent, CoatOfArmComponent child);

        public event ChildrenChangedEventHandler ChildAdded;
        public event ChildrenChangedEventHandler ChildRemoved;

        /// <summary>
        /// Adds the child to the list of children (the child is added only if it does not already exist).
        /// </summary>
        /// <param name="child">The child.</param>
        public virtual void AddChild(CoatOfArmComponent child)
        {
            //add the child in the logic tree
            if (Children == null || Children.Any(ch => ch == child)) return;
            
            Children.Add(child);

            //add the child in the flattened view for CoAViewer purpose
            if (child.RootParent != null && child.RootParent is CoatOfArmViewModel)
                ((CoatOfArmViewModel)child.RootParent).AddChildInDictionary(child);

            //event "child have been added"
            if (ChildAdded != null)
                ChildAdded(this, child);
        }

        /// <summary>
        /// Removes the child. If it exists in the collection
        /// </summary>
        /// <param name="child">The child.</param>
        public virtual void RemoveChild(CoatOfArmComponent child)
        {
            if (Children == null) return;
            Children.Remove(child);
            
            //remove the child from the flattened view for CoAViewer purpose
            if(child.RootParent!=null && child.RootParent is CoatOfArmViewModel)
                ((CoatOfArmViewModel)child.RootParent).RemoveChildFromDictionary(child);

            if (ChildRemoved != null)
                ChildRemoved(this, child);
        }


        private ObservableCollection<CoatOfArmComponent> _children;

        /// <summary>
        /// Gets or sets the children of this logic tree.
        /// Use the Add and RemoveChild to manipulate the Children (security and event managing)
        /// </summary>
        /// <value>
        /// The children.
        /// </value>
        public ObservableCollection<CoatOfArmComponent> Children
        {
            get { return _children; }
            protected set
            {
                if (value == _children)
                    return;
                _children = value;
                OnPropertyChanged("Children");
            }
        }


        private bool _isExpanded;
        /// <summary>
        /// Gets/sets whether the TreeViewItem 
        /// associated with this object is expanded.
        /// </summary>
        public bool IsExpanded
        {
            get { return _isExpanded; }
            set
            {
                if (value != _isExpanded)
                {
                    _isExpanded = value;
                    if (!value)
                        IsSelected = false;
                    OnPropertyChanged("IsExpanded");
                }

                // Expand all the way up to the root.
                if (_isExpanded && Parent != null)
                    Parent.IsExpanded = true;
            }
        }

        private bool _isSelected;

        /// <summary>
        /// Gets/sets whether the TreeViewItem 
        /// associated with this object is selected.
        /// </summary>
        public bool IsSelected
        {
            get { return _isSelected; }
            set
            {
                if (value == _isSelected) return;
                if(value)
                    IsExpanded = true;
                _isSelected = value;
                if (value)
                    IsExpanded = true;
                OnPropertyChanged("IsSelected");
                //OnPropertyChanged("ComponentName");
                //OnPropertyChanged("PropertiesData");
                //OnPropertyChanged("Children");
            }
        }

        #endregion //IChildContainer

        #region ITreeViewSearchable

        /// <summary>
        /// For treeview research purpose
        /// </summary>
        /// <param name="text">The text.</param>
        /// <returns></returns>
        public bool NameContainsText(string text)
        {
            if (String.IsNullOrEmpty(text) || String.IsNullOrEmpty(ComponentName))
                return false;
            return ComponentName.IndexOf(text, StringComparison.InvariantCultureIgnoreCase) > -1;
        }

        #endregion // ITreeViewSearchable

        #region INotifyPropertyChanged

        /// <summary>
        /// Called when a property change, always update the propertiesData
        /// </summary>
        /// <param name="propertyName">Name of the property.</param>
        protected void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged == null) return;
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            if(propertyName!="PropertiesData")
                PropertyChanged(this, new PropertyChangedEventArgs("PropertiesData"));
        }

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion
    }
}
